/*******************************************************************************
 * 作者:    水煮肉片饭(27185709@qq.com)
 * 版本:    CocosCreator v3.8.2
 * 描述:    支持3D旋转的Sprite
 * 1、修改组件的angle属性，可以让SpriteFrame图片3D旋转
 *    angle是一个Vec2，分别代表绕X轴、Y轴旋转的角度
 * 2、网格越多图片越保真，但运算量越大
 *    不影响效果前提下，网格数越少越好。
 * 3、透视程度（取值范围[0,1]，0代表不透视，1代表极限透视）
*******************************************************************************/
import * as cc from 'cc';
import { DEV, EDITOR, JSB } from 'cc/env';
export enum SizeMode { 'CUSTOM: 自定义尺寸', 'TRIMMED: 原始尺寸裁剪透明像素', 'RAW: 图片原始尺寸' }
const { ccclass, property, menu } = cc._decorator;
@ccclass
@menu('Public/SpriteFlipper')
export class SpriteFlipper extends cc.UIRenderer {
    @property({ type: cc.SpriteAtlas, readonly: true })
    protected atlas: cc.SpriteAtlas = null;
    @property
    private _spriteFrame: cc.SpriteFrame = null;
    @property({ type: cc.SpriteFrame })
    get spriteFrame() { return this._spriteFrame; }
    set spriteFrame(value: cc.SpriteFrame) {
        this._spriteFrame = value;
        this.updateSpriteFrame();
        this.updateUV();
    }
    @property
    private _sizeMode: SizeMode = SizeMode['TRIMMED: 原始尺寸裁剪透明像素'];
    @property({ type: cc.Enum(SizeMode) })
    get sizeMode() { return this._sizeMode; }
    set sizeMode(value: SizeMode) {
        this._sizeMode = value;
        this.updateSizeMode();
    }
    @property
    private _grid: cc.Vec2 = cc.v2(3, 3);
    @property({ displayName: DEV && '网格', tooltip: DEV && '网格越多图像越保真，但性能消耗也越大' })
    get grid(): Readonly<cc.Vec2> { return this._grid; }
    set grid(value: cc.Vec2) {
        this._grid = cc.v2(Math.max(~~value.x, 1), Math.max(~~value.y, 1));
        this.createBuffer();
        this.updatePoints();
        this.updateUV();
        this.updateIData();
        this.markForUpdateRenderData();
    }
    @property
    private _angle: cc.Vec2 = new cc.Vec2(0, 0);
    @property({ displayName: DEV && '旋转角度' })
    get angle(): Readonly<cc.Vec2> { return this._angle; }
    set angle(value: cc.Vec2) {
        this._angle = value;
        this.markForUpdateRenderData();
    }
    @property
    private _obDisRatio: number = 0.5;
    @property({ min: 0, max: 1, step: 0.1, slide: true, displayName: DEV && '透视程度', tooltip: DEV && '0代表不透视，1代表极限透视\n透视程度较大时会失真\n建议增加网格提升保真度' })
    get obDisRatio() { return this._obDisRatio; }
    set obDisRatio(value: number) {
        this._obDisRatio = value;
        this.updateObDis();
        this.markForUpdateRenderData();
    }
    private ut: cc.UITransform = null;
    private obDis: number = 0;                      //观察者与图片的距离
    private points: cc.Vec2[] = [];                 //未翻转前的网格顶点坐标

    public __preload() {
        super.__preload();
        this._assembler = {
            updateColor: this.updateColor.bind(this),
            updateRenderData: this.onFlushed.bind(this),
            fillBuffers: this.fillBuffer.bind(this),
        };
        this.ut = this.node.getComponent(cc.UITransform) ?? this.node.addComponent(cc.UITransform);
    }
    public onLoad() {
        super.onLoad();
        this.updateSpriteFrame();
        this.createBuffer();
        this.updateUV();
        this.updateIData();
        this.updateObDis();
        this.updatePoints();
        this.node.on(cc.NodeEventType.ANCHOR_CHANGED, this.onAnchorChanged, this);
        this.node.on(cc.NodeEventType.SIZE_CHANGED, this.onSizeChanged, this);
    }
    public onDestroy() {
        super.onDestroy();
        this.node.off(cc.NodeEventType.ANCHOR_CHANGED, this.onAnchorChanged, this);
        this.node.off(cc.NodeEventType.SIZE_CHANGED, this.onSizeChanged, this);
    }
    //根据透视程度，计算观察者与图片的距离
    private updateObDis() {
        let ut = this.ut, ax = ut.anchorX, ay = ut.anchorY, obDisRatio = this._obDisRatio;
        let disX = ut.width * (ax > 0.5 ? ax : 1 - ax) * (obDisRatio === 0 ? 1e8 : Math.pow(2, 3 * (1 - obDisRatio)));
        let disY = ut.height * (ay > 0.5 ? ay : 1 - ay) * (obDisRatio === 0 ? 1e8 : Math.pow(2, 3 * (1 - obDisRatio)));
        this.obDis = Math.max(disX, disY);
    }
    //计算网格顶点未旋转状态下的XY
    private updatePoints() {
        let ut = this.ut, cw = ut.width, ch = ut.height, ax = ut.anchorX, ay = ut.anchorY;
        let l = -cw * ax, r = cw * (1 - ax), b = -ch * ay, t = ch * (1 - ay);
        let col = this._grid.x, row = this._grid.y;
        let points = this.points;
        for (let i = 0, rowSize = row + 1; i < rowSize; ++i) {
            for (let j = 0, colSize = col + 1; j < colSize; ++j) {
                points[colSize * i + j] = cc.v2(l + (r - l) * j / col, b + (t - b) * i / row);
            }
        }
    }
    //设置顶点个数和三角形个数
    private createBuffer() {
        this.destroyRenderData();
        let renderData = this._renderData = this.requestRenderData();
        renderData.dataLength = (this._grid.x + 1) * (this._grid.y + 1);
        renderData.resize(renderData.dataLength, 3 * this._grid.x * this._grid.y << 1);
        this._updateColor();
        this.updateIData();
    }
    //Web平台，将renderData的数据提交给GPU渲染，renderData.data使用世界坐标
    //原生平台并不会执行该函数，引擎另外实现了渲染函数，renderData.data使用本地坐标
    private fillBuffer() {
        let renderData = this._renderData;
        let chunk = renderData.chunk;
        if (this.node.hasChangedFlags || renderData.vertDirty) {
            let vb = chunk.vb;
            let data = renderData.data;
            let m = this.node.worldMatrix;
            let m00 = m.m00, m01 = m.m01, m02 = m.m02, m04 = m.m04, m05 = m.m05, m06 = m.m06, m12 = m.m12, m13 = m.m13, m14 = m.m14;
            for (let i = 0, id = 0, len = vb.length, step = renderData.floatStride; i < len; i += step, ++id) {
                let x = data[id].x, y = data[id].y;
                vb[i] = m00 * x + m04 * y + m12;
                vb[i + 1] = m01 * x + m05 * y + m13;
                vb[i + 2] = m02 * x + m06 * y + m14;
            }
            renderData.vertDirty = false;
        }
        let vid = chunk.vertexOffset;
        let meshBuffer = chunk.meshBuffer;
        let iData = meshBuffer.iData;
        let ib = chunk.ib;
        for (let i = 0, len = renderData.indexCount, offset = meshBuffer.indexOffset; i < len; iData[offset++] = vid + ib[i++]);
        meshBuffer.indexOffset += renderData.indexCount;
    }
    //如果材质为空，则设置为默认材质
    public updateMaterial() {
        super['updateMaterial']();
        this._customMaterial ??= this.getSharedMaterial(0);
    }
    //修改节点锚点后，重新计算obDis和points
    private onAnchorChanged() {
        this.updateObDis();
        this.updatePoints();
    }
    //修改节点尺寸后，更新顶点数据，并根据sizeMode设置图片宽高
    private onSizeChanged() {
        this.updatePoints();
        if (!this._spriteFrame) return;
        let ut = this.node.getComponent(cc.UITransform);
        switch (this._sizeMode) {
            case SizeMode['TRIMMED: 原始尺寸裁剪透明像素']:
                let rect = this._spriteFrame['_rect'];
                if (ut.width === rect.width && ut.height === rect.height) return;
                break;
            case SizeMode['RAW: 图片原始尺寸']:
                let size = this._spriteFrame['_originalSize'];
                if (ut.width === size.width && ut.height === size.height) return;
                break;
        }
        this._sizeMode = SizeMode['CUSTOM: 自定义尺寸'];
    }
    //可以传入cc.SpriteFrame图集帧（支持合批，推荐），或单张图片cc.Texture2D
    private updateSpriteFrame() {
        let frame = this.spriteFrame;
        if (!frame) { this.atlas = null; return; }
        this._renderData && (this._renderData.textureDirty = true);
        this.updateSizeMode();
        if (EDITOR) {
            if (!frame['_atlasUuid']) { this.atlas = null; return; }
            cc.assetManager.loadAny(frame['_atlasUuid'], (err: Error, asset: cc.SpriteAtlas) => {
                if (err) { this.atlas = null; return; }
                this.atlas = asset;
            });
        }
    }
    //根据尺寸模式，修改节点尺寸
    private updateSizeMode() {
        if (!this._spriteFrame) return;
        let ut = this.node.getComponent(cc.UITransform) ?? this.node.addComponent(cc.UITransform);
        switch (this._sizeMode) {
            case SizeMode['TRIMMED: 原始尺寸裁剪透明像素']: ut.setContentSize(this._spriteFrame['_rect'].size); break;
            case SizeMode['RAW: 图片原始尺寸']: ut.setContentSize(this._spriteFrame['_originalSize']); break;
        }
    }
    //实时运算的顶点数据内容
    private onFlushed() {
        let frame = this._spriteFrame;
        if (!frame) return;
        this.updateXY();
        this._renderData.updateRenderData(this, frame);
    }
    //计算XY坐标
    private updateXY() {
        let data = this._renderData.data;
        let points = this.points;
        let obDis = this.obDis;
        let radianX = this._angle.x * Math.PI / 180;
        let cosX = Math.cos(radianX), sinX = Math.sin(radianX);
        let radianY = this._angle.y * Math.PI / 180;
        let cosY = Math.cos(radianY), sinY = Math.sin(radianY);
        //根据透视的相似三角形规则，计算顶点透视旋转radian弧度后的坐标
        for (let i = data.length - 1; i > -1; --i) {
            let point = points[i], ratio = obDis * obDis / ((obDis - point.y * sinX) * (obDis + point.x * sinY));
            data[i].x = point.x * ratio * cosY;
            data[i].y = point.y * ratio * cosX;
        }
    }
    //计算UV坐标
    private updateUV() {
        let vb = this._renderData.chunk.vb;
        let size = this._grid.x + 1;
        let deltX = 1 / this._grid.x, deltY = 1 / this._grid.y;
        for (let i = 3, len = vb.length, step = this._renderData.floatStride, id = 0; i < len; i += step, ++id) {
            vb[i] = (id % size) * deltX;
            vb[i + 1] = ~~(id / size) * deltY;
        }
        this.fitUV(vb);
    }
    //自动适配UV，修改顶点uv数据后需主动调用该函数
    private fitUV(vb: Float32Array) {
        if (!this.spriteFrame) return;
        let uv = this.spriteFrame.uv;
        if (this.spriteFrame['_rotated']) {
            var uvL = uv[0], uvB = uv[1], uvW = uv[4] - uvL, uvH = uv[3] - uvB;
            for (let i = 3, len = vb.length, step = this._renderData.floatStride; i < len; i += step) {
                let tmp = vb[i];
                vb[i] = uvL + vb[i + 1] * uvW;
                vb[i + 1] = uvB + tmp * uvH;
            }
        } else {
            var uvL = uv[0], uvB = uv[1], uvW = uv[2] - uvL, uvH = uv[5] - uvB;
            for (let i = 3, len = vb.length, step = this._renderData.floatStride; i < len; i += step) {
                vb[i] = uvL + vb[i] * uvW;
                vb[i + 1] = uvB + vb[i + 1] * uvH;
            }
        }
    }
    //计算顶点颜色
    private updateColor() {
        let renderData = this._renderData;
        let vb = renderData.chunk.vb;
        let color = this.color, r = color.r / 255, g = color.g / 255, b = color.b / 255, a = color.a / 255;
        for (let i = 5, len = vb.length, step = renderData.floatStride; i < len; i += step) {
            vb[i] = r;
            vb[i + 1] = g;
            vb[i + 2] = b;
            vb[i + 3] = a;
        }
    }
    //计算顶点索引
    private updateIData() {
        let ib = this._renderData.chunk['_ib'];
        let size = this._grid.x + 1;
        for (let i = 0, len = ib.length, id = 0; i < len; ++id) {
            (id + 1) % size === 0 && ++id;
            ib[i++] = id;
            ib[i++] = id + 1;
            ib[i++] = id + size;
            ib[i++] = id + size;
            ib[i++] = id + 1;
            ib[i++] = id + size + 1;
        }
        JSB && this._renderData.chunk.setIndexBuffer(ib);
    }
    protected _render(render: any) {
        render.commitComp(this, this._renderData, this._spriteFrame, this._assembler, null);
    }
    protected _canRender(): boolean {
        return super._canRender() && !!(this._spriteFrame?.texture);
    }
}